package cn.demoncat.util.fastdfs;

import cn.demoncat.util.conn.ConnPool;
import cn.demoncat.util.conn.ConnPoolFactory;
import cn.demoncat.util.conn.ConnTask;
import cn.demoncat.util.exception.BizErrorRuntimeException;
import cn.demoncat.util.exception.BizRuntimeException;
import cn.demoncat.util.exception.IoRuntimeException;
import cn.demoncat.util.exception.ParamRuntimeException;
import cn.demoncat.util.fastdfs.config.FdProperties;
import cn.demoncat.util.fastdfs.constant.FdConstant;
import cn.demoncat.util.io.FileNameUtil;
import cn.demoncat.util.io.FileSizeUtil;
import cn.demoncat.util.io.ImgUtil;
import cn.demoncat.util.io.IoUtil;
import cn.demoncat.util.io.config.ImgProperties;
import cn.demoncat.util.io.entity.ImgParam;
import cn.demoncat.util.lang.StringUtil;
import cn.demoncat.util.lang.constant.StatusConstant.Common;
import cn.demoncat.util.lang.constant.StringConstant;
import cn.demoncat.util.lang.msg.FileMsg;
import net.coobird.thumbnailator.filters.Watermark;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * FastDFS工具
 * 
 * @author 延晓磊
 *
 * @since 2019年11月4日
 */
@Component("fdUtil")
public class FdUtil {
	
	// Tracker Server 连接池
	private final ConnPool<TrackerServer> pool;
	// 配置
	private final FdProperties fdProperties;
	@Resource
	private ImgProperties imgProperties;
	
	/**
	 * 初始化FD工具
	 */
	@Autowired
	public FdUtil(FdProperties fdProperties) {
		// 检查配置
		if (StringUtils.isBlank(fdProperties.getUrl())) {
			throw new IoRuntimeException("初始化FastDFS客户端失败：没有可用的 Tracker Server");
		}
		this.fdProperties = fdProperties;
		// Tracker Server 连接池
		pool = new ConnPool<>(new ConnPoolFactory<>(TrackerServer.class, fdProperties), fdProperties);
	}
	
	/**
	 * 获取FastDFS连接并执行操作
	 * 
	 * @param task	操作任务
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年11月5日
	 */
	public <R>R execute(ConnTask<StorageClient,R> task) {
		// 获取Tracker Server
		TrackerServer trackerServer = pool.conn();
		// 获取Tracker Client
		TrackerClient trackerClient = new TrackerClient(trackerServer, fdProperties);
		// 获取Storage Client
		StorageClient storageClient = new StorageClient(trackerClient);
		// 执行操作
		try {
			return task.execute(storageClient);
		} finally {
			// 关闭Tracker Server
			pool.close(trackerServer);
		}
	}
	
	/**
	 * 删除文件
	 *
	 * @param fullFileName	文件全名：不限制前缀/
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月22日
	 */
	public void delete(String fullFileName) {
		if (StringUtils.isBlank(fullFileName)) {
			throw new BizRuntimeException(FileMsg.FILE_URL_IS_NULL);
		}
		execute(conn -> {
			conn.delete(fullFileName);
			return Boolean.TRUE;
		});
	}
	
	/**
	 * 批量删除文件
	 *
	 * @param fullFileNames	[文件全名（不限制前缀/）]
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月22日
	 */
	public void delete(List<String> fullFileNames) {
		if (fullFileNames == null || fullFileNames.isEmpty()) {
			throw new BizRuntimeException(FileMsg.FILE_URL_IS_NULL);
		}
		execute(conn -> {
			for (String fullFileName : fullFileNames) {
				conn.delete(fullFileName);
			}
			return Boolean.TRUE;
		});
	}
	 
	/**
	 * 下载文件
	 *
	 * @param fullFileName	文件全名：不限制前缀/
	 * @return 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月22日
	 */
	public byte[] download(String fullFileName){
		if (StringUtils.isBlank(fullFileName)) {
			throw new BizRuntimeException(FileMsg.FILE_URL_IS_NULL);
		}
		return execute(conn -> {
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			conn.download(fullFileName, out);
			return out.toByteArray();
		});
	}

	/**
	 * 下载文件
	 *
	 * @param fullFileName	文件全名：不限制前缀/
	 * @param out			输出流（未关闭）
	 * @return 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月22日
	 */
	public void download(String fullFileName, OutputStream out){
		if (StringUtils.isBlank(fullFileName)) {
			throw new BizRuntimeException(FileMsg.FILE_URL_IS_NULL);
		}
		execute(conn -> {
			conn.download(fullFileName, out);
			return Boolean.TRUE;
		});
	}
	
	/**
	 * 上传文件：直接上传，不可续传
	 *
	 * @param group		分组，为空时随机
	 * @param ext		文件扩展名(不带.)
	 * @param file  	文件
	 * 
	 * @return groupName/fileName.ext
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月22日
	 */
	public String upload(String group, String ext, byte[] file){
		if (file == null) {
			throw new BizRuntimeException(FileMsg.UPLOAD_FILE_IS_NULL);
		}
		return execute(conn -> FdNameUtil.mergeFileName(conn.upload(group, ext, file)));
	}
	
	/**
	 * 上传文件：直接上传，不可续传
	 *
	 * @param group		分组，为空时随机
	 * @param ext		文件扩展名(不带.)
	 * @param file  	文件（未关闭）
	 * 
	 * @return groupName/fileName.ext
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月22日
	 */
	public String upload(String group, String ext, InputStream file){
		return upload(group, ext, IoUtil.readToBytes(file));
	}
	
	/**
	 * 批量上传文件：直接上传，不可续传
	 *
	 * @param group		分组，为空时随机
	 * @param ext		文件扩展名(不带.)
	 * @param files  	文件
	 * 
	 * @return [groupName/fileName.ext]
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月22日
	 */
	public List<String> upload(String group, String ext, byte[][] files){
		if (files == null) {
			throw new BizRuntimeException(FileMsg.UPLOAD_FILE_IS_NULL);
		}
		return execute((ConnTask<StorageClient, List<String>>) conn -> {
			// 获取Storage Server
			StorageServer server = conn.getTrackerClient().getUploadStorage(group);
			// 批量上传
			ArrayList<String> list = new ArrayList<>(files.length);
			for (byte[] bs : files) {
				list.add(FdNameUtil.mergeFileName(conn.upload(server, ext, bs)));
			}
			// 关闭Storage Server
			server.close();
			return list;
		});
	}
	
	/**
	 * 批量上传文件：直接上传，不可续传
	 *
	 * @param group		分组，为空时随机
	 * @param ext		文件扩展名(不带.)
	 * @param files  	文件（未关闭）
	 * 
	 * @return [groupName/fileName.ext]
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月22日
	 */
	public List<String> upload(String group, String ext, InputStream[] files){
		if (files == null) {
			throw new BizRuntimeException(FileMsg.UPLOAD_FILE_IS_NULL);
		}
		return execute((ConnTask<StorageClient, List<String>>) conn -> {
			// 获取Storage Server
			StorageServer server = conn.getTrackerClient().getUploadStorage(group);
			// 批量上传
			ArrayList<String> list = new ArrayList<>(files.length);
			for (InputStream bs : files) {
				list.add(FdNameUtil.mergeFileName(conn.upload(server, ext, IoUtil.readToBytes(bs))));
			}
			// 关闭Storage Server
			server.close();
			return list;
		});
	}
	
	/**
	 * 文件上传：根据阀值确定直接上传/分块续传
	 *
	 * @param file		文件（流未关闭）
	 * @param group		分组（为空时随机）
	 * @param ext		文件扩展名(不带.)
	 * 
	 * @return groupName/fileName.ext
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月22日
	 */
	public String uploadFile(InputStream file, String group, String ext){
		return uploadFile(IoUtil.readToBytes(file), group, ext);
	}
	
	/**
	 * 文件上传：根据阀值确定直接上传/分块续传
	 *
	 * @param file		文件
	 * @param group		分组（为空时随机）
	 * @param ext		文件扩展名(不带.)
	 * 
	 * @return groupName/fileName.ext
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月22日
	 */
	public String uploadFile(byte[] file, String group, String ext){
		// 文件校验
		if (file == null) {
			throw new BizRuntimeException(FileMsg.UPLOAD_FILE_IS_NULL);
		}
		// 阀值判断
		int limit = fdProperties.getLimit() > FdConstant.APPEND_LIMIT_MIN ? fdProperties.getLimit() : FdConstant.APPEND_LIMIT_MIN;
		if (file.length < limit) {
			// 直接上传
			return this.upload(group, ext, file);
		}else {
			// 分块续传
			return execute(conn ->{
				// 上传可更新文件
				int start = 0;
				int end = FdConstant.APPEND_BUFF_SIZE;
				String[] gf = conn.uploadAppender(group, ext, ArrayUtils.subarray(file, start, end));
				// 获取Storage Server
				// 续传文件
				try (StorageServer server = conn.getTrackerClient().getUpdateStorage(gf[0], gf[1])) {
					while (end < file.length) {
						start += FdConstant.APPEND_BUFF_SIZE;
						end += FdConstant.APPEND_BUFF_SIZE;
						if (end > file.length) {
							conn.append(server, gf[1], ArrayUtils.subarray(file, start, file.length));
						} else {
							conn.append(server, gf[1], ArrayUtils.subarray(file, start, end));
						}
					}
				} catch (Exception e) {
					throw new BizErrorRuntimeException(FileMsg.UPLOAD_FILE_ERROR, e);
				}
				// 关闭Storage Server
				return FdNameUtil.mergeFileName(gf);
			});
		}
	}
	
	/**
	 * 图片上传
	 * 
	 * @param file		文件（流未关闭）
	 * @param group		分组（为空时随机）
	 * @param ext		文件扩展名(不带.)
	 * @param slaves 	图片缩略尺寸标识(多个用,分隔，生成多份文件，URL为URL-slave)
	 * 
	 * @return groupName/fileName.ext
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年5月24日
	 */
	public String uploadImg(InputStream file, String group, String ext, String slaves) {
		return uploadImg(IoUtil.readToBytes(file), group, ext, slaves);
	}
	
	/**
	 * 图片上传
	 * 
	 * @param file		文件
	 * @param group		分组（为空时随机）
	 * @param ext		文件扩展名(不带.)
	 * @param slaves 	图片缩略尺寸标识(多个用,分隔，生成多份文件，URL为URL-slave)
	 * 
	 * @return groupName/fileName.ext
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年5月24日
	 */
	public String uploadImg(byte[] file, String group, String ext, String slaves) {
		// 文件校验
		if (file == null) {
			throw new BizRuntimeException(FileMsg.UPLOAD_FILE_IS_NULL);
		}
		// 执行任务
		return execute(conn -> {
			// 上传原图
			String[] gf = conn.upload(group, ext, file);
			// 上传副本
			if (StringUtils.isNotBlank(slaves)) {
				ByteArrayInputStream in;
				ByteArrayOutputStream out;
				// 获取Storage Server
				try (StorageServer server = conn.getTrackerClient().getUpdateStorage(gf[0], gf[1])) {
					for (String slave : StringUtil.split(slaves)) {
						in = new ByteArrayInputStream(file);
						out = new ByteArrayOutputStream();
						// 缩放图片
						ImgUtil.zooms(in, out, slave);
						// 上传副本
						conn.uploadSlave(server, gf[1], slave, ext, out.toByteArray());
					}
				}
				// 关闭Storage Server
			}
			return FdNameUtil.mergeFileName(gf);
		});
	}
	
	/**
	 * WEB文件/图片上传：文件自动分块上传；图片支持压缩、水印
	 * 
	 * <pre>
	 * 图片缩放标识：
	 * 100 		width=100(对称拉伸)
	 * 100o100	width=100|height=100(智能拉伸)
	 * 100x100	width=100&height=100(强制拉伸)
	 * L10000	尺寸大于10000时，以10000为限额计算比例并压缩
	 * Q0.1		以0.1的比例压缩
	 * </pre>
	 * 
	 * @param file		文件（流未关闭）
	 * @param group		分组（为空时随机）
	 * @param saveName	是否保存文件名：0不保存，1保存（默认，以#号拼接文件名，文件名非法时修剪），2强力保存（以#号拼接文件名，文件名非法时报错）
	 * @param slaves 	图片缩略尺寸标识(多个用,分隔，生成多份文件，URL为URL-slave)
	 * @param zoom		图片缩略尺寸标识(单个，对原图进行缩放)
	 * @param water		图片水印
	 * 
	 * @return groupName/fileName[-slave].ext[#fileOriginalName]
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月22日
	 */
	public String upload(MultipartFile file, String group, String saveName, String slaves, String zoom, Watermark water) {
		// 文件校验
		if (file == null) {
			throw new ParamRuntimeException(FileMsg.UPLOAD_FILE_IS_NULL);
		}
				
		// 是否为图片
		boolean isImg = ImgUtil.check(file.getContentType());
				
		// 判断文件尺寸
		long fileSize = file.getSize();
		if (isImg) {
			if(fileSize > imgProperties.getImgMax()){
				throw new BizRuntimeException(FileMsg.UPLOAD_IMG_TO_BIG + FileSizeUtil.sizeString(imgProperties.getImgMax()));
			}
		}else {
			if(fileSize > imgProperties.getFileMax()){
				throw new BizRuntimeException(FileMsg.UPLOAD_FILE_TO_BIG + FileSizeUtil.sizeString(imgProperties.getFileMax()));
			}
		}
				
		// 是否保存文件名
		if (StringUtils.isBlank(saveName)) {
			saveName = Common.YES;
		}
		boolean isSaveName = !Common.NO.equals(saveName);
		// 文件原名
		String oldFileName = file.getOriginalFilename();
		if (isSaveName) {
			// 校验文件名
			if (Common.isYes(saveName)) {
				oldFileName = FileNameUtil.trim(oldFileName);
			}else{
				FileNameUtil.checkForce(oldFileName);
			}
		}
		// 文件后缀
		String ext = FileNameUtil.getExtension(oldFileName);
				
		// 获取文件
		byte[] bytes;
		try {
			bytes = file.getBytes();
		} catch (IOException e) {
			throw new BizRuntimeException(FileMsg.UPLOAD_FILE_ERROR, e);
		}
		
		// 上传
		String url;
		if (isImg) {
			// 原图处理：根据zoom和water进行压缩和水印
			if (StringUtils.isNotBlank(zoom) || water != null) {
				bytes = ImgUtil.out(bytes, ImgParam.build().zoom(zoom).watermark(water));
			}
			// 图片上传：根据slave创建副本
			url = this.uploadImg(bytes, group, ext, slaves);
		}else {
			// 文件上传：根据limit区分直接上传或分块续传
			url = uploadFile(bytes, group, ext);
		}
		// 整合文件URL
		return isSaveName ? url + StringConstant.HASH + oldFileName : url;
	}
	
	/**
	 * Base64文件/图片上传：文件自动分块上传；图片支持压缩、水印
	 * 
	 * <pre>
	 * 图片缩放标识：
	 * 100 		width=100(对称拉伸)
	 * 100o100	width=100|height=100(智能拉伸)
	 * 100x100	width=100&height=100(强制拉伸)
	 * L10000	尺寸大于10000时，以10000为限额计算比例并压缩
	 * Q0.1		以0.1的比例压缩
	 * </pre>
	 * 
	 * @param file			文件base64
	 * @param group			分组（为空时随机）
	 * @param oldFileName	文件原名
	 * @param ext 			文件后缀名（不带.）：通过ext获取 > 通过oldFileName获取 > 通过contentType获取 > 默认png；如果是图片将导致zoom生效，所以非图片文件应保证ext/oldFileName/contentType非空
	 * @param saveName		是否保存文件名：0不保存，1保存（默认，以#号拼接文件名，文件名非法时修剪），2强力保存（以#号拼接文件名，文件名非法时报错）
	 * @param slaves 		图片缩略尺寸标识(多个用,分隔，生成多份文件，URL为URL-slave)
	 * @param zoom			图片缩略尺寸标识(单个，对原图进行缩放)
	 * @param water			图片水印
	 * 
	 * @return groupName/fileName[-slave].ext[#fileOriginalName]
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月22日
	 */
	public String upload(String file, String group, String oldFileName, String ext, String saveName, String slaves, String zoom, Watermark water) {
		// 文件校验
		if (StringUtils.isBlank(file)) {
			throw new BizRuntimeException(FileMsg.UPLOAD_FILE_IS_NULL);
		}
		// 去除HTML文件前缀：data:image/png;base64,……
		String type = null;
		if (file.startsWith("data:") && file.contains(";base64")) {
			int i = file.indexOf(';');
			// 文件类型：image/png
			type = file.substring(5, i);
			// 文件
			file = file.substring(i+8);
		}
		
		// 获取文件后缀
		if (StringUtils.isBlank(ext)) {
			// 通过文件名获取
			if (StringUtils.isNotBlank(oldFileName)) {
				ext = FileNameUtil.getExtension(oldFileName);
			}else {
				// 通过contentType获取
				if (StringUtils.isNotBlank(type)) {
					ext = StringUtils.split(type,StringConstant.SEPARATOR)[1];
				} else {
					// 默认PNG
					ext = "png";
				}
			}
		}
		
		// 是否为图片
		boolean isImg = ImgUtil.check(type == null ? ext : type);
		
		// 是否保存文件名
		if (StringUtils.isBlank(saveName)) {
			saveName = Common.YES;
		}
		boolean isSaveName = !Common.NO.equals(saveName);
				
		// 解析请求参数(Base64数据)
		byte[] bytes = Base64.decodeBase64(file);
				
		// 判断文件尺寸
		long fileSize = bytes.length;
		if (isImg) {
			if(fileSize > imgProperties.getImgMax()){
				throw new BizRuntimeException(FileMsg.UPLOAD_IMG_TO_BIG + FileSizeUtil.sizeString(imgProperties.getImgMax()));
			}
		}else {
			if(fileSize > imgProperties.getFileMax()){
				throw new BizRuntimeException(FileMsg.UPLOAD_FILE_TO_BIG + FileSizeUtil.sizeString(imgProperties.getFileMax()));
			}
		}
		
		// 上传
		String url;
		if (isImg) {
			// 原图处理：根据zoom和water进行压缩和水印
			if (StringUtils.isNotBlank(zoom) || water != null) {
				bytes = ImgUtil.out(bytes, ImgParam.build().zoom(zoom).watermark(water));
			}
			// 图片上传：根据slave创建副本
			url = this.uploadImg(bytes, group, ext, slaves);
		}else {
			// 文件上传：根据limit区分直接上传或分块续传
			url = uploadFile(bytes, group, ext);
		}
		
		// 整合文件URL
		if (isSaveName) {
			// 校验文件名
			if (Common.isYes(saveName)) {
				oldFileName = FileNameUtil.trim(oldFileName, url);
			}else{
				FileNameUtil.checkForce(oldFileName);
			}
			return url + StringConstant.HASH + oldFileName;
		}else {
			return url;
		}
	}
	
	/**
	 * 文本上传：直接上传
	 * 
	 * @param text			文本内容
	 * @param ext			文件后缀/文件类型（不带.）：html/txt
	 * @param group			分组	
	 * @param oldFileName	文件原名（非空时将以#拼接在URL后面，非法时自动剪切）
	 * 
	 * @return groupName/fileName.ext[#oldFileName]
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年4月17日
	 */
	public String upload(String text, String ext, String group, String oldFileName) {
		// 文件校验
		if (StringUtils.isBlank(text)) {
			throw new BizRuntimeException(FileMsg.UPLOAD_FILE_IS_NULL);
		}
		// 上传
		String url = this.upload(group, ext, StringUtil.getBytes(text));
		// 拼接原名
		if (StringUtils.isNotBlank(oldFileName)) {
			return url + StringConstant.HASH + FileNameUtil.trim(oldFileName);
		}else {
			return url;
		}
	}
	
	
}
